/**
	Copyright (c) 2010 ATTi 
	All rights reserved.
	Author: Michael Shu
	Date: 2/25/2010
*/

public without sharing class CaseIOGClass {	
	public static String User_Prefix  = GeneralConstants.User_Prefix;
	public static String Queue_Prefix  = GeneralConstants.Queue_Prefix;
	
	// Record Type Ids
	public static String IOGIncidentRecordTypeID =RecordTypeHelper.GetRecordTypeID('Case',  'IOG Incident');
	public static String IOGChangeTaskRecordTypeID = RecordTypeHelper.GetRecordTypeID('Case', 'IOG Change Task');
	public static String IOGInquiryRecordTypeID = RecordTypeHelper.GetRecordTypeID('Case', 'IOG Inquiry');
	public static String IOGContentTaskRecordTypeID = RecordTypeHelper.GetRecordTypeID('Case', 'IOG Content Task');
	public static String IOGCancelTaskRecordTypeID = RecordTypeHelper.GetRecordTypeID('Case', 'IOG Cancel Task');
	public static String IOGClaimTaskRecordTypeID = RecordTypeHelper.GetRecordTypeID('Case', 'IOG Claim Task');
	public static String IOGTroubleTicketRecordTypeID = RecordTypeHelper.GetRecordTypeID('Case', 'IOG Trouble Ticket');
	public static Set<String> STUBRecordTypeIDs   = new set<String>{IOGChangeTaskRecordTypeID, IOGInquiryRecordTypeID, IOGContentTaskRecordTypeID
	                                                              , IOGCancelTaskRecordTypeID, IOGClaimTaskRecordTypeID, IOGTroubleTicketRecordTypeID}; 
	public static Set<String> AllIOGRecordTypeIDs = new set<String>{IOGChangeTaskRecordTypeID, IOGInquiryRecordTypeID,   IOGContentTaskRecordTypeID
		                                                          , IOGCancelTaskRecordTypeID, IOGClaimTaskRecordTypeID, IOGTroubleTicketRecordTypeID
		                                                          , IOGIncidentRecordTypeID}; 
	// Requst type : Incident and CRF
	public static String NOTDEFINED = 'Not Defined';
	public static String ChangeTaskType = 'Change Task';
	public static String InquiryType = 'Inquiry';
	public static String ContentTaskType = 'Content Task';
	public static String CancelTaskType = 'Cancel Task';
	public static String ClaimTaskType = 'Claim Task';
	public static String TroubleTicketType = 'Trouble Ticket';
	public static Set<String> RequestTypes = new set<String>{ChangeTaskType, InquiryType, ContentTaskType, CancelTaskType, ClaimTaskType, TroubleTicketType}; 
	// Case origin 
	public static String EMAIL = 'Email';
	public static String CRF = 'CRF';
	public static String INTERNAL = 'Internal'; // added by plo
	// Status
	public static String OPEN = 'Open';
	public static String CLOSED = 'Closed';
	public static String COMPLETE = 'Complete';
	public static String CANCELLED = 'Cancelled';
	public static String LIVE = 'Live';
	public static String OUTED = 'Outed';
	public static Set<String> FI_END_STATUS = new Set<String>{COMPLETE, CANCELLED, LIVE, OUTED};
	public static String IN_PROGRESS = 'In Progress';
	public static String READY = 'Ready';
	public static String ESCALATED = 'Escalated';
	public static String IN_WORKFLOW = 'In Workflow';	
	// IR Priority
	public static String  STANDARD       = 'Standard';
	public static String  ESCALATION     = 'Escalation';
	//public static String  TOP            = 'TOP';  
	public static Map<String, Integer> Priorities = new Map<String, Integer>{'' => -1,STANDARD => 0, ESCALATION => 1}; 
	// Product list	
	public static String IYP_LISTING_AD = 'IYP Listing Ad';
	public static Map<String,String> CRFWestProducts = new Map<String,String>{'ATT411'         => 'IOG IR - Sales & Customer Support'
																	  , 'Local Online Display' => 'IOG IR - Sales & Customer Support'
																	  , 'Mobile'               => 'IOG IR - Sales & Customer Support'
																	  , 'OPM'                  => 'IOG IR - Sales & Customer Support'
																	  , 'Pay-Per-Call'         => 'IOG IR - Sales & Customer Support'
																	  , 'Website'              => 'IOG IR - Sales & Customer Support'
																	  , 'YP360'                => 'IOG IR - Sales & Customer Support'
																	  , 'Domain/Email'         => 'IOG IR - Support - Domain/Email'
																	  , 'YPConnect'            => 'IOG IR - Support - YPConnect'
																	  , 'YP SEO'               => 'IOG IR - Support - YPSEO'};
	public static Map<String,String> CRFEastProducts = new Map<String,String>{'Domain/Email' => 'IOG IR - Support - Domain/Email'
																	  , 'YPClicks'           => 'IOG IR - Support - YPClicks'
																	  , 'YPConnect'          => 'IOG IR - Support - YPConnect'
																	  , 'YP SEO'             => 'IOG IR - Support - YPSEO'};
	
	public static String InRegionWest = 'In-Region West';
	public static String InRegionEast = 'In-Region East';
	public static String OutOfRegion  = 'Out-Of-Region';
																	  	
	private static Map<String, ID> queueIds;
	//private static Boolean isFirstValidate = true; // validate only once
	private static Boolean isFITrigger = false;	
	private static User curLoginUser;
	private static Integer NUM_OF_COLS  = 11;
	private static Boolean isOORSalesUser = GeneralConstants.PROFILE_SFA_OOR_SALES.contains(getLoginUser().Profile.Name); // check if current user is a SFA OOR Sales user
	
	// Custom Exception class
	public class ConfigurationException extends Exception {}
	
	private static String mapFulfillToCaseProductGroupNames(String s){
		Map<String, String> fufillmentToCase = new Map<String, String>{
		    'loda 1.0' => 'Local Online Display'
		   ,'loda 2.0' => 'Local Online Display'
		   ,'loda enhanced (moda)' => 'Local Online Display'
		   ,'yahoo display ad' => 'Local Online Display'
		   ,'mobile website' => 'Mobile'
		   ,'basic video' => 'Video'
		   ,'premium video' => 'Video'
		   ,'self submit video' => 'Video'
		   ,'b-website oor' => 'Website'
		   ,'basic website' => 'Website'
		   ,'enhanced website' => 'Website'
		   ,'essential website' => 'Website'
		   ,'other website' => 'Website'
		   ,'premium website' => 'Website'
		   ,'real website' => 'Website'
		   ,'yp360' => 'YP360'
		   ,'ypclicks' => 'YPClicks'
		   ,'other search' => 'YPConnect'
		   ,'ypconnect' => 'YPConnect'
		   ,'att411' => 'ATT411'
		   ,'audio ad' => 'Audio'
		   ,'banner' => 'Banner/Diamond'
		   ,'coupons' => 'Coupon'
		   ,'limited inventory' => 'IYP Listing Ad'
		   ,'listing' => 'IYP Listing Ad'
		   ,'pao' => 'IYP Listing Ad'
		   ,'points' => 'IYP Listing Ad'
		   ,'more info' => 'IYP More Info'
		   ,'opm' => 'OPM'
		   ,'pay per call' => 'Pay-Per-Call'
		   ,'seo' => 'YP SEO'
		   ,'domain/email' => 'Domain/Email'
		   ,'iyp free listing' => 'IYP Free Listing'
		   ,'adreach' => 'Unknown'
		   ,'barcodes' => 'Unknown'
		   ,'dna' => 'Unknown'
		   ,'legacy' => 'Unknown'
		   ,'lm berry' => 'Unknown'
		   ,'metered ad' => 'Unknown'
		   ,'other' => 'Unknown'
		   ,'other charge' => 'Unknown'
		   ,'unassigned bundle' => 'Unknown'
		   ,'unassigned udac' => 'Unknown'
		   ,'west udac' => 'Unknown'
		   ,'no item' => 'No Item'
		};
		String retStr = 'Unknown';
		if(s != null && fufillmentToCase.containsKey(s.toLowerCase())){
			retStr = fufillmentToCase.get(s.toLowerCase());
		}
		return retStr;
	}
		
	private static String mapRequestTypes(String request){
		String rst;
		if(request == ChangeTaskType){
	   	   rst = IOGChangeTaskRecordTypeID;
	    }else if(request == InquiryType){
	   	   rst = IOGInquiryRecordTypeID;
	    }else if(request == ContentTaskType){
	   	   rst = IOGContentTaskRecordTypeID; 
	    }else if(request == CancelTaskType){
	   	   rst = IOGCancelTaskRecordTypeID; 
	    }else if(request == ClaimTaskType){
	   	   rst = IOGClaimTaskRecordTypeID; 
	    }else if(request == TroubleTicketType){
	   	   rst = IOGTroubleTicketRecordTypeID; 
	    }else{
	       rst = IOGChangeTaskRecordTypeID;
	    }		
		return rst;
	}

    private static void setIncidentIRPriorityAndOtherFields(Case c){
    	c.Total_Case_Count__c = 0;
    	c.CSS_CRF_RequestType__c = (c.CSS_CRF_RequestType__c == null)? NOTDEFINED : c.CSS_CRF_RequestType__c;
    	if(c.Origin == EMAIL){
    		if(Priorities.get(c.IR_Priority__c) < Priorities.get(ESCALATION) && c.Priority != null && c.Priority.toLowerCase().contains(ESCALATION.toLowerCase())){
    			c.IR_Priority__c = ESCALATION;
    			c.Status = ESCALATED;
    		}
		}else if(c.Origin == CRF){
    		c.IR_Priority__c = STANDARD;
    		if(c.CRF_Request_Type__c != null){
	    		if(c.CRF_Request_Type__c.toLowerCase().contains('change request')){
	    			c.CSS_CRF_RequestType__c = ChangeTaskType;
	    			c.IR_Priority__c = STANDARD;
	    		}else if(c.CRF_Request_Type__c.toLowerCase().contains('inquiry')){
	    			c.CSS_CRF_RequestType__c = InquiryType;
	    			c.IR_Priority__c = STANDARD;
	    		}else if(c.CRF_Request_Type__c.toLowerCase().contains('submit document')){
	    			c.CSS_CRF_RequestType__c = ContentTaskType;
	    			c.IR_Priority__c = STANDARD;
	    		}else if(c.CRF_Request_Type__c.toLowerCase().contains('escalate')){
	    			c.CSS_CRF_RequestType__c = ChangeTaskType;
	    			c.IR_Priority__c = ESCALATION;
	    			c.Status = ESCALATED;
	    		}else{
	    			c.CSS_CRF_RequestType__c = ChangeTaskType;
	    			c.IR_Priority__c = STANDARD;
	    		}
    		}
    	}else if(c.Status == ESCALATED){
    		c.IR_Priority__c = ESCALATION;
    	}
    }
    
    private static void validateStubsInWorkflowWithoutFI(Case c, Map<ID,Case> oldMap){
    	if(c.RecordTypeId == IOGChangeTaskRecordTypeID){
	    	if(c.Status == IN_WORKFLOW && c.Fulfillment_Item__c == null && c.RPI__c == null){
	    		c.Status.addError(' Please click the "Initiate Change" button to set the Status to "In Workflow".');
	    	}
	    	if(oldMap != null){ // update
	    		Case oldCase = oldMap.get(c.Id);
	    		if(c.RPI__c != oldCase.RPI__c && !isFITrigger && c.Fulfillment_Item__c == oldCase.Fulfillment_Item__c && c.Fulfillment_Item__c != null){
	    			c.addError(' You may not update the RPI because this Stub Case is already linked to a Fulfillment Item. ');
	    		}
	    	}
    	}
    }
	
	private static void validateChangeTaskStubsLockInWorkflowStatus(List<Case> stubs, Map<ID,Case> oldMap){
		List<Case> cancelledChangeTasks = new List<Case>();
		for(Case c : stubs){
			Case oldCase = oldMap.get(c.Id);
			if(c.RecordTypeId == IOGChangeTaskRecordTypeId && c.Fulfillment_Item__c != null){
				if(c.Status != oldCase.Status && oldCase.Status == IN_WORKFLOW && !FI_END_STATUS.contains(c.Fulfillment_Item_Status__c)){
					if(c.Status != CANCELLED){
						c.Status.addError(' Case Status is locked. To close the case, please mark the Fulfillment Item as \'Complete\' or \'Cancelled\'.');
					}else{
						cancelledChangeTasks.add(c);
					}
				}
			}
		}
		if(!cancelledChangeTasks.isEmpty()){
			Set<ID> fiIDs = new Set<ID>();
			Set<ID> csIDs = new Set<ID>();
			for(Case c : cancelledChangeTasks){
				fiIDs.add(c.Fulfillment_Item__c);
				csIDs.add(c.Id);
			}
			Map<Id, Fullfilment_Item__c> fis = new Map<Id, Fullfilment_Item__c>([select id, (select id from Cases__r where id Not IN: csIDs and isClosed = false) 
			                                                                     from Fullfilment_Item__c where id IN: fiIDs]);
			for(Case c : cancelledChangeTasks){
				List<Case> otheropens = fis.get(c.Fulfillment_Item__c).Cases__r;
				if(otheropens.isEmpty()){
					c.Status.addError(' Can\'t Change the Case Status to \'Cancelled\' because No other OPEN IOG Change Task Case is linked to this Fulfillment Item. To close the case, please mark the Fulfillment Item as \'Complete\' or \'Cancelled\'.');
				}
			}
		}
	}
	
	private static void validateIssueNotNullWhenCompleteStubs(Case c, Map<ID,Case> oldMap, User loginuser){
		if(!loginuser.Profile.Name.startsWith('System Administrator')){
			if((oldMap == null || oldMap != null && c.Status != oldMap.get(c.Id).Status) && c.Status == COMPLETE && (c.AFrontier_Issue__c == null || c.AFrontier_Reason__c == null)){
				c.Status.addError(' You must complete the Issue/Reason fields to Close this case.');
			}
		}
	}
	
	private static void validateIncidentClosedWithRegionValue(Case c, Case oldCase){
		if(c.Status != oldCase.Status && c.Status == CLOSED && c.Region__c == null){
			c.Status.addError(' IOG Region can\'t be blank. Please make sure the Case has an Account and the Account has a valid Region Code.');
		}
	}	
	
	private static void validateParentIdInNewStub(Case c){
		if(c.parentId == null){
			c.parentId.addError(' You can only create stubs from an Incident Case. TIP: You can open a new window by holding your SHIFT key down and clicking on any link in salesforce.com, then copy and paste information you may have input here');
		}
	}
	
	private static Boolean isManager(User loginuser){
		String s = loginuser.UserRole.Name;
		return loginuser.Profile.Name.startsWith('System Administrator') 
		       || (s != null && (!s.toLowerCase().contains('rep') || s.toLowerCase().contains('support l2')));
	}
	
	private static void validateIncidentCaseEscalated(Case c, Map<ID,Case> oldMap, User loginuser){ 
		String s = loginuser.UserRole.Name;
		if(!isManager(loginuser)){
			Boolean isError = false;
			if(oldMap == null){
				if(c.Status == ESCALATED){
					isError = true;
				}
			}else{
				Case oldCase = oldMap.get(c.Id);
				if(c.Status != oldCase.Status && c.Status == ESCALATED){
					isError = true;
				}
			}
			if(isError){
				c.Status.addError(' You are unable to escalate Cases. Please reach out to your supervisor or manager to escalate this Case.');
			}
		}
	}

	private static void validateIRPriorityEscalatedInStub(Case c, Map<ID,Case> oldMap, User loginuser){
		String s = loginuser.UserRole.Name;
		if(!isManager(loginuser)){
			Boolean isError = false;
			if(oldMap == null){
				if(c.IR_Priority__c == ESCALATION){
					isError = true;
				}
			}else{
				Case oldCase = oldMap.get(c.Id);
				if(c.IR_Priority__c != oldCase.IR_Priority__c && c.IR_Priority__c == ESCALATION && c.Fulfillment_Item__c == oldCase.Fulfillment_Item__c){
					isError = true;
				}
			}
			if(isError){
				c.IR_Priority__c.addError(' You are unable to set IOG Priority to Escalation. Please reach out to your supervisor or manager to escalate this Case.');
			}
		}
	}
		
	private static void validateWhenCaseOriginIsCRFOrEmail(List<Case> cases, Map<ID,Case> oldMap, User loginuser){ 
		if(!loginuser.Profile.Name.startsWith('System Administrator')){
			Set<ID> pCaseIds = new Set<ID>(); 
			List<Case> stubCRFOrEmails = new List<Case>();
			Map<String, String> errormsg = new Map<String, String>{
			                    CRF   => ' This Case Origin applies only to cases originating from the change request form on the web.',
			                   	EMAIL => ' This Case Origin applies only to cases originating from one of the support email inboxes.'
			                   	};
			for(Case c : cases){
				if(c.ParentId != null){ // stubs
					if(oldMap != null){ // update
						Case oldCase = oldMap.get(c.Id);
						if(c.Origin != oldCase.Origin && (c.Origin == CRF || c.Origin == EMAIL)){
							pCaseIds.add(c.ParentId);
							stubCRFOrEmails.add(c);
						}
					}else{ // insert
						if((c.Origin == CRF || c.Origin == EMAIL)){
							pCaseIds.add(c.ParentId);
							stubCRFOrEmails.add(c);
						}				
					}
				}else{ // incidents
					if(oldMap != null){ // update
						Case oldCase = oldMap.get(c.Id);
						if(c.Origin != oldCase.Origin && (c.Origin == CRF || c.Origin == EMAIL)){
							c.Origin.addError(errormsg.get(c.Origin));
						}
					}else{ // insert
						if(c.Origin == CRF || (c.Origin == EMAIL && c.SuppliedEmail == null)){ // allow users to use the outlook connector to create cases
							c.Origin.addError(errormsg.get(c.Origin));
						}
					}
				}
			}
			if(!pCaseIds.isEmpty()){
				Map<ID, Case> pCases = new Map<ID, Case>([select Id, Origin from Case where Id IN: pCaseIds]);
				for(Case c : stubCRFOrEmails){
					Case p = pCases.get(c.ParentId);
					if(p != null && c.Origin != p.Origin){
						c.Origin.addError(errormsg.get(c.Origin));
					}
				}
			}
		}
	}	
	
	private static void validateReOpenStubsStillHasOpenIncident(List<Case> cases, Map<ID, Case> oldMap){
		if(!cases.isEmpty()){
			Set<ID> reopenIds = new Set<ID>();
			for(Case c : cases){
				Case oldCase = oldMap.get(c.Id);
				if(oldCase.Status != c.Status){
					if(oldCase.Status == COMPLETE || oldCase.Status == CANCELLED){
						reopenIds.add(c.Id);
					}
				}
			}		
			if(!reopenIds.isEmpty()){
				Map<ID, Case> reopenned = new Map<ID, Case>([select id, Parent.isClosed from Case where id IN: reopenIds]);
				for(Case c : cases){
					if(reopenned.containsKey(c.Id) && reopenned.get(c.Id).Parent.isClosed){
						c.Status.addError(' You can\'t re-open this case, because the parent case is closed.');
					}
				}
			}
		}
	}
			
	private static void validateIncidentCaseWhenClosedororReady(List<Case> cases, Map<ID, Case> oldMap, User loginuser){
		if(!cases.isEmpty()){
			if(oldMap == null){ // insert
				if(!loginuser.Profile.Name.startsWith('System Administrator')){
					for(Case c : cases){
						if(c.Status == CLOSED || c.Status == READY){
							c.Status.addError(' At least one Stub case must exist prior to setting the Status to Ready/Closed.');
						}
					}
				}
			}else{ // update
				Set<ID> closedreadycsIds = new Set<ID>();
				for(Case c : cases){
					Case oldCase = oldMap.get(c.Id);
					if(oldCase.Status != c.Status){
						if((c.Status == CLOSED || c.Status == READY)){
							closedreadycsIds.add(c.Id);
						}
					}
				}
				if(!closedreadycsIds.isEmpty()){
					Map<ID, Case> casewithStubs = new Map<ID, Case>([select id, (select id, isClosed from cases) from Case where Id IN: closedreadycsIds]);
					for(Case p : cases){
						if(casewithStubs.containsKey(p.Id)){
							List<Case> stubs = casewithStubs.get(p.Id).cases;
							if(stubs != null && !stubs.isEmpty()){
								for(Case s : stubs){
									if(!s.isClosed){
										if(p.Status == CLOSED){
											p.Status.addError(' All related Stubs must be completed prior to closing this case.');
										}else if(p.Status == READY){
											p.Status.addError(' Only automated workflow can set the Status to "Ready". Please update the Status.');
										}
										break;
									}
								}
							}else{
								p.Status.addError(' At least one Stub case must exist prior to changing the Status to Ready/Closed.');
							}
						}
					}
				}
			}
		}
	}
	
	private static void validateStubCaseRecordTypeChange(Case c, Case oldCase){
		if(c.RecordTypeId != oldCase.RecordTypeId){
			if(c.isClosed){
				c.RecordTypeId.addError(' You can\'t change the Case Record Type of a closed case.');
			}else if(!STUBRecordTypeIDs.contains(c.RecordTypeId) && STUBRecordTypeIDs.contains(oldCase.RecordTypeId)){
				c.RecordTypeId.addError(' You can\'t change the Case Record Type to this one.');	
			}else if(c.AFrontier_Issue__c == oldCase.AFrontier_Issue__c && c.AFrontier_Issue__c != null ){
				c.AFrontier_Issue__c.addError(' This is an invalid Issue Value, please choose a different one.');
			}
		}
	}	
	
	private static void validateClosedCase(Case c, Case oldCase, User loginuser){
		Boolean isRefreshFromParentToStubs = STUBRecordTypeIDs.contains(c.RecordTypeId) 
			&& (c.AccountId != oldCase.AccountId || c.ContactId != oldCase.ContactId || c.Account_Manager__c != oldCase.Account_Manager__c || c.Origin != oldCase.Origin);
		if(!loginuser.Profile.Name.startsWith('System Administrator') && oldCase.isClosed && !isRefreshFromParentToStubs){
			if(oldCase.AFrontier_Ticket_ID__c != null){
				c.addError('You cannot make changes to a case that is Closed, and that has been synced with AFrontier. If you need to make further comments on this account, please open a new Case.');
			}	
			if(c.Status == oldCase.Status){
				c.addError('You cannot update the closed case fields except for the Status.'); 	
			}
		}
	}
	
	private static void setIRPriorityToEscalation(Case c, Case oldCase){
		if(c.Status == ESCALATED && !oldCase.IsEscalated && Priorities.get(c.IR_Priority__c) < Priorities.get(ESCALATION)){
			c.IR_Priority__c = ESCALATION;
		}
	}
			
	private static void refreshFromParentCases(List<Case> cases){
		Set<ID> parentCaseIds = new Set<ID>();
		for(Case c : cases) {
			parentCaseIds.add(c.ParentId);			
		}	
		if(!parentCaseIds.isEmpty()){
			Map<ID, Case> parentCases = new Map<ID, Case>([select Id, Origin, Priority, Subject, Description, CSS_CRF_RVP__c, CSS_CRF_GM__c, CSS_CRF_BusinessName__c, CSS_CRF_ContactNumber__c, CSS_CRF_CustomerContactName__c
			                                                      , CSS_CRF_CustomerContactTel__c, CRF_Request_Type__c, Case_CSS_Customer_ID__c, Account_Manager__c, AccountId, ContactId, Last_Queue_Owner__c 
			                                               from Case where Id IN: parentCaseIds]);
			for(Case c : cases){
			   if(c.ParentId != null && parentCases.containsKey(c.ParentId)){
				   Case pcase = parentCases.get(c.ParentId);
				   c.AccountId = pcase.AccountId;
				   c.ContactId = pcase.ContactId;
				   c.Origin = pcase.Origin;
				   c.Account_Manager__c = pcase.Account_Manager__c;
				   c.Priority = pcase.Priority;
			   	   c.CSS_CRF_RVP__c = pcase.CSS_CRF_RVP__c;
			   	   c.CSS_CRF_GM__c  = pcase.CSS_CRF_GM__c;
			   	   c.CRF_Request_Type__c = pcase.CRF_Request_Type__c;
			   	   c.Case_CSS_Customer_ID__c = pcase.Case_CSS_Customer_ID__c;
			   	   c.CSS_CRF_BusinessName__c = pcase.CSS_CRF_BusinessName__c;
			   	   c.CSS_CRF_ContactNumber__c = pcase.CSS_CRF_ContactNumber__c;
			   	   c.CSS_CRF_CustomerContactName__c = pcase.CSS_CRF_CustomerContactName__c;
			   	   c.CSS_CRF_CustomerContactTel__c = pcase.CSS_CRF_CustomerContactTel__c;			
				   if(c.Last_Queue_Owner__c == null && pcase.Last_Queue_Owner__c != null){
				   	   c.Last_Queue_Owner__c = pcase.Last_Queue_Owner__c;
				   }
				   if(c.Subject == null){
				   	   c.Subject = pcase.Subject;
				   }
				   if(c.Description == null){
				   	   c.Description = pcase.Description;
				   }
			   }	
			}						
		}		
	}
    
	private static void changeIncidentCaseOwnerInInsertAndUpdate(List<Case> cases, Map<ID, Case> oldMap){
		for(Case c : cases){
			String qName;
			if(oldMap != null){ // escalate only
				Case oldCase = oldMap.get(c.Id);
				if(c.Status != oldCase.Status && c.Status == ESCALATED && !c.IsEscalated){
					c.IsEscalated = true;
					c.CSS_InitialEscalationDateTime__c = DateTime.now();
					qName = getIOGEscalateQueueOwnersByRegions(c);
				}
			}else{ // insert 
				if(c.Status == ESCALATED){
					c.IsEscalated = true;
					c.CSS_InitialEscalationDateTime__c = DateTime.now();					
					if(c.Origin != EMAIL /*AD-24883 s146*/){ // escalate only
						qName = getIOGEscalateQueueOwnersByRegions(c);
					}
				}
				if(c.Origin == CRF){// implement the new IOG CRF case assignment rule here
					if(c.CSS_CRF_RequestType__c == ContentTaskType && c.Region__c != OutOfRegion){
						qName = 'IOG IR - Receiving Team';
					}else{
						if(c.Status == ESCALATED){
							qName = getIOGEscalateQueueOwnersByRegions(c);
						}else{
							qName = getIOGCustomerSupportQueueOwnersByRegions(c);
						}
						if(c.Region__c == InRegionWest && isEastEffectiveRegion(c)){ 
							if(c.Status == ESCALATED){
								qName = 'IOG IR - Sales Escalation';
							}else{
								qName = 'IOG IR - Sales & Customer Support';
							}								
						}
					}					
				}
			}
			setOwnerIdAndLastQueueOwner(c, qName);
		}
	}	

	/*
        added by plo, AD-31915
	    check if current user is in SFA OOR profile and force to set the values on insert:
	    		   
	    - Status: Open, IOG Priority: Standard, Case Origin: Internal
	    - match professional contact by ATTUID__c if found
	*/	
	private static void setFieldsForSFAOORUsers(List<Case> sfa_incidents){
		if(!sfa_incidents.isEmpty()){
			ID sfa_contact_id;
			if(getLoginUser().ATTUID__c != null){
				List<Contact> cList = [SELECT Id FROM Contact WHERE ATTUID__c = :getLoginUser().ATTUID__c ORDER BY LastModifiedDate DESC];
				if(!cList.isEmpty()) // use the last modified one if more than one is found
				{
					sfa_contact_id = cList[0].Id;
				}
			}
			for(Case c : sfa_incidents){
				c.Status = OPEN;
				c.IR_Priority__c = STANDARD;
				c.Origin = INTERNAL;
				if(c.ContactId == null && sfa_contact_id != null){
					c.ContactId = sfa_contact_id;
				}
				//String qName = getIOGCustomerSupportQueueOwnersByRegions(c);
				//setOwnerIdAndLastQueueOwner(c, qName);
			}
		}
	}
		
	public static void setOwnerIdAndLastQueueOwner(Case c, String qName){
		if(qName != null){
			c.IOG_Queue_Owner__c = qName;
			c.OwnerId = getQueueId(qName);
			c.Last_Queue_Owner__c = c.OwnerId;
		}
	}
	
	// used by EmailToNewCase.trigger	
	public static String getIOGEscalateQueueOwnersByRegions(Case c){
		String rst;
		if(c.Region__c == OutOfRegion){
			rst = 'IOG OOR - Sales Escalation';
		}else if(c.Region__c == InRegionWest){
			rst = 'IOG WEST - Sales Escalation';
		}else{ // In-Region East
			rst = 'IOG IR - Sales Escalation';
		}
		return rst;
	}
	
    // used by EmailToNewCase.trigger
	public static String getIOGCustomerSupportQueueOwnersByRegions(Case c){
		String rst;
		if(c.Region__c == OutOfRegion){
			rst = 'IOG OOR - Sales & Customer Support';
		}else if(c.Region__c == InRegionWest){
			rst = 'IOG WEST - Sales & Customer Support';
		}else{ // In-Region East
			rst = 'IOG IR - Sales & Customer Support';
		}
		return rst;
	}
					
	private static void setRPIDateStampAndTaskDueDate(Case c){
		if(c.Task_Due_Date__c == null){
			DateTime stamp = (c.CreatedDate == null)? DateTime.now() : c.CreatedDate;
			c.Task_Due_Date__c = stamp.addDays(5);
		}
	}
	
	private static void keepEscalated(Case c){
		if(c.IsEscalated){
			c.IsEscalated = true;
		}
	}

	private static void linkToItem(Case c, Case oldCase){
		if(c.CRF_PACKAGE__c != oldCase.CRF_PACKAGE__c && c.CRF_PACKAGE__c != null){
			String[] columns = c.CRF_PACKAGE__c.split('<col>', NUM_OF_COLS); // column delimiter
			populatePackageFields(c, null, columns);
		}
	}

	private static Boolean isEastEffectiveRegion(Case c){
		Boolean rst = true;
		if(c.CRF_PACKAGE__c != null){
			String[] sRows = c.CRF_PACKAGE__c.split('<row>');
			for(String r : sRows){
				if(r != null){
					String[] columns = r.split('<col>', NUM_OF_COLS); // column delimiter 
					Integer FulfillmentProductGroup = 1;
					String sIOGAdType = mapFulfillToCaseProductGroupNames(columns[FulfillmentProductGroup]); 
					if(!CRFWestProducts.containsKey(sIOGAdType)){
						rst = false;
						break;
					}
				}
			}
		}else{
			rst = false;
		}
		return rst;
	}
				
	//      0    |           1                 |        2       |     3        |     4     |         5         |     
	//STUB TYPE<col>Fulfillment Product Group<col>Feature Code<col>Feature ID<col>Item ID<col>Item Region Code<col>
	//       6       |       7         |           8              |      9      |        10
	//Price Plan ID<col>Product Issue<col>Product/Directory Code<col>UDAC Code<col>UDAC Description<row>
	private static void populatePackageFields(Case cs, Case parentCase, String[] cols){
		if(cols.size() != NUM_OF_COLS){
			throw new ConfigurationException(' Fail Reason : CRF PACKAGE field format mismatch with the definition. cols.size() = ' + cols.size());
		}
		Integer StubType=0
		       ,FulfillmentProductGroup=1
		       ,FeatureCode=2
		       ,FeatureID=3
		       ,ItemID=4
		       ,ItemRegionCode=5
		       ,PricePlanId=6
		       ,ProductIssue=7
		       ,ProductDirectoryCode=8
		       ,UDACCode=9
		       ,UDACDescription=10; 
		if(parentCase != null){
			String strStubType = cols[StubType];
			if(strStubType != null && strStubType != ''){
				String trimmedStubType = strStubType.trim().replace('IOG ', '');
				if(RequestTypes.contains(trimmedStubType)){
					cs.RecordTypeId = mapRequestTypes(trimmedStubType);
				}else{
					throw new ConfigurationException(' Fail Reason : Stub Type is not defined : [Stub Type] : ' + strStubType);
				}
			}else{
				cs.RecordTypeId = mapRequestTypes(parentCase.CSS_CRF_RequestType__c);
			}			
		}
		cs.Product_code__c = cols[ProductDirectoryCode];
		cs.Product_Issue__c = cols[ProductIssue];
		cs.Item_ID__c = cols[ItemID] == ''? null : Integer.valueOf(cols[ItemID]);
		cs.UDAC_Code__c = cols[UDACCode];
		cs.Fulfillment_Product_Group_Name__c = cols[FulfillmentProductGroup];
		cs.Product_Family__c = mapFulfillToCaseProductGroupNames(cs.Fulfillment_Product_Group_Name__c);
		cs.Feature_Code__c = cols[FeatureCode].toLowerCase() == 'n/a'? null : cols[FeatureCode];
		cs.Feature_Id__c = cols[FeatureID] == ''? null : Integer.valueOf(cols[FeatureID]);
		cs.Price_Plan_Id__c = cols[PricePlanId] == ''? null : Integer.valueOf(cols[PricePlanId]);
		cs.FI_Region_Code__c = cols[ItemRegionCode];
		cs.UDAC_Description__c = cols[UDACDescription];
	}
	
	private static void setStubIOGAdTypeAndRecordTypeId(Case cs, Case parentCase, String pName){
		cs.Product_Family__c = pName;
		cs.RecordTypeId = mapRequestTypes(parentCase.CSS_CRF_RequestType__c);
	}
	
	public static void setCaseFieldsToFixedvalues(Case cs, Case parentCase){
		cs.Status = OPEN;
		cs.CRF_PACKAGE__c = null;
		cs.ParentId = parentCase.Id;
		cs.CSS_ProductFamily__c = null;
		cs.CSS_CRF_RequestType__c = null;
	 	cs.IsEscalated = false;
	 	cs.CSS_InitialEscalationDateTime__c = null; 
	 	cs.SuppliedEmail = GeneralConstants.checkAndTransformWebEmail(cs.SuppliedEmail);
	 	if(parentCase.Status == ESCALATED){
			cs.IR_Priority__c = ESCALATION;
		}
		String qOwnerName;
		if(parentCase.CreatedDate > DateTime.Now().addMinutes(-1) && parentCase.Origin == CRF && parentCase.Status != ESCALATED && parentCase.CSS_CRF_RequestType__c != ContentTaskType){
			if(parentCase.Region__c == InRegionEast && CRFEastProducts.containsKey(cs.Product_Family__c)){
				qOwnerName = CRFEastProducts.get(cs.Product_Family__c);
			}else if(parentCase.Region__c == InRegionWest && CRFWestProducts.containsKey(cs.Product_Family__c)){
				qOwnerName = CRFWestProducts.get(cs.Product_Family__c);
			}	
		} 
		setOwnerIdAndLastQueueOwner(cs, qOwnerName);
		Database.DMLOptions dmo = new Database.DMLOptions();
        dmo.assignmentRuleHeader.useDefaultRule= false;
	    cs.setOptions(dmo);			   
	}	
	
	public static void setIncidentCasesOnlyInsert(list<Case> incidentsInsert){
		List<Case> incidentsFromSFAOORUsers = new List<Case>();
		for(Case c : incidentsInsert){
			setIncidentIRPriorityAndOtherFields(c);
			if(isOORSalesUser){
				incidentsFromSFAOORUsers.add(c);
				c.Status = OPEN; // force it to OPEN
			}
		}
		setFieldsForSFAOORUsers(incidentsFromSFAOORUsers);
		changeIncidentCaseOwnerInInsertAndUpdate(incidentsInsert, null);
	}

	public static void setIncidentCasesOnlyUpdate(list<Case> incidentsUpdate, Map<ID, Case> oldMap){
		for(Case c : incidentsUpdate){
			Case oldCase = oldMap.get(c.Id);
			setIRPriorityToEscalation(c, oldCase);
			if(isOORSalesUser){
				c.Status = OPEN; // force it to OPEN
			}
		}
		changeIncidentCaseOwnerInInsertAndUpdate(incidentsUpdate, oldMap);
	}
	
	public static void setStubCasesInsert(List<Case> stubsInsert){
		List<Case> changetasks = new List<Case>();
		for(Case c : stubsInsert){
			setRPIDateStampAndTaskDueDate(c);
		}
		refreshFromParentCases(stubsInsert);
	}
	
	public static void setStubCasesUpdate(List<Case> stubsUpdate, Map<ID, Case> oldMap){
		Map<ID, Case> updateStubCases = new Map<ID, Case>(); 
		for(Case c : stubsUpdate){
			Case oldCase = oldMap.get(c.Id);
			setRPIDateStampAndTaskDueDate(c);
			linkToItem(c, oldCase);
			if(c.Fulfillment_Item__c != oldCase.Fulfillment_Item__c && c.Fulfillment_Item__c != null){
				updateStubCases.put(c.Id, c);
			}
		}
        updateCaseFromFulfillmentItem(null, updateStubCases); 		
	}

	public static void setAllIOGCases(List<Case> alliogcases){ 
		for(Case c : alliogcases){
			keepEscalated(c);
		}
	}
	
	public static void checkAllValidationRules(List<Case> cases, Map<ID, Case> oldMap){
		List<Case> incidentCases = new List<Case>();
		List<Case> stubCases = new List<Case>();
		User loginuser = getLoginUser();
		
		for(Case c : cases){
			if(c.RecordTypeId == IOGIncidentRecordTypeId){ // insert and update
				incidentCases.add(c);
				validateIncidentCaseEscalated(c, oldMap, loginuser);
			}else if(STUBRecordTypeIDs.contains(c.RecordTypeId)){
				validateStubsInWorkflowWithoutFI(c, oldMap);
				validateIssueNotNullWhenCompleteStubs(c, oldMap, loginuser);
				//if(isFirstValidate){
				validateIRPriorityEscalatedInStub(c, oldMap, loginuser);
				//}
			}
			if(oldMap != null){ // update only
				Case oldCase = oldMap.get(c.Id);
				validateStubCaseRecordTypeChange(c, oldCase);
				validateClosedCase(c, oldCase, loginuser);
				if(c.RecordTypeId == IOGIncidentRecordTypeId){
					validateIncidentClosedWithRegionValue(c, oldCase);
				}
				if(STUBRecordTypeIDs.contains(c.RecordTypeId)){
					stubCases.add(c);
				}
			}else{ // insert only
				if(STUBRecordTypeIDs.contains(c.RecordTypeId)){
					validateParentIdInNewStub(c);
				}
			}
		}
		validateIncidentCaseWhenClosedororReady(incidentCases, oldMap, loginuser);
		validateReOpenStubsStillHasOpenIncident(stubCases, oldMap);
		validateChangeTaskStubsLockInWorkflowStatus(stubCases, oldMap);
		validateWhenCaseOriginIsCRFOrEmail(cases, oldMap, loginuser);
		//isFirstValidate = false;
	}
     
	public static void insertNewSubCasesFromCRFPackage(Set<ID> incidentcsids){
		if(!incidentcsids.isEmpty()){
			List<Case> incidents = (List<Case>) CustomCloneFunctionClass.queryObjs('Case', incidentcsids);
			List<Case> newstubs = new List<Case>();
			for(Case p : incidents){
				if(p.CRF_PACKAGE__c != null){
					String[] rows = p.CRF_PACKAGE__c.split('<row>'); // row delimiter 
					for(String r : rows){
						if(r != null){
							String[] columns = r.split('<col>', NUM_OF_COLS); // column delimiter 
							Case newcs = (Case) p.clone(false, true); // clear Id, deep clone
							populatePackageFields(newcs, p, columns);
							setCaseFieldsToFixedvalues(newcs, p);
							newstubs.add(newcs);
						}
					}
				}
			}
			
			if(!newstubs.isEmpty()){
				String ids = '';
				for(String s : incidentcsids){
					ids += s + ';'; 
				}
				checkInvalidWebEmailExceptionAndTryAgain(newstubs, true, false, ' Fail Reason : inside Automatically create new stubs from the CRF Package fail ! id = ' + ids + ' error: '); //insert
			}
		}
	}
	
	public static void insertNewSubCasesFromAffectedProducts(Set<ID> incidentcsids, Map<ID, Case> newMap){
		if(!incidentcsids.isEmpty()){
			List<Case> newstubs = new List<Case>();
			List<Case> incidents = [select Id, CSS_ProductFamily__c, (select Product_Family__c from cases order by Product_Family__c asc) from Case where Id IN: incidentcsids];
			for(Case incident : incidents){		
				String products = '';
				List<Case> stubs = incident.cases;
				for(Case c : stubs){
					if(c.Product_Family__c != null && !products.contains(c.Product_Family__c)){
						products += c.Product_Family__c + ';';
					}
				}
				String[] sel_products = incident.CSS_ProductFamily__c.split(';');
				for(String s : sel_products){
					if(!products.contains(s)){
						Case parent = newMap.get(incident.Id);
						Case newcs = (Case) parent.clone(false, true); // clear Id, deep clone
						setStubIOGAdTypeAndRecordTypeId(newcs, parent, s);
						setCaseFieldsToFixedvalues(newcs, parent);
						newstubs.add(newcs);
					}
				}
			}
			if(!newstubs.isEmpty()){
				String ids = '';
				for(String s : incidentcsids){
					ids += s + ';'; 
				}
				try{
					insert newstubs;
				}catch(Exception ex){
					throw new ConfigurationException(' Fail Reason : Automatically create stubs from the Affected Products fail! id = ' + ids + ' error: ');
					GeneralConstants.mailAnyExceptionToMe('CaseIOG Class', 'Automatically create stubs from the Affected Products fail id = ' + ids + ' error: ' , ex);
				}
			}
		}
	}

	// update top ir priority, status, Incident Request Type from stubs
	public static void updateParentIncidentCases(Set<ID> incidentIds){
		Boolean rst = true;
		String debug = '';
		if(!incidentIds.isEmpty()){
			List<Case> incidentWithStubs = [select CSS_CRF_RequestType__c, CSS_ProductFamily__c, Total_Case_Count__c, isClosed, Status, IR_Priority__c, Region__c
			                                       , (select Product_Family__c, Status, IR_Priority__c, RecordTypeId, RecordType.Name, isClosed from cases order by Product_Family__c asc) 
			                                from Case 
			                                where Id IN: incidentIds and RecordTypeID =: IOGIncidentRecordTypeID];
			List<Case> parenttoUpdate = new List<Case>();
			for(Case p : incidentWithStubs){
				List<Case> stubs = p.cases;
				Integer total=stubs.size(), opened=0, progress=0, contenttask=0;
				String toppriority = '', stubType='', products='';
				for(Case c : stubs){
					if(!c.isClosed){
						opened++;
					}
					if(c.Status != OPEN){
						progress++;
					}
					if(c.RecordTypeId == IOGContentTaskRecordTypeID){
						contenttask++;
					}
					if(Priorities.get(c.IR_Priority__c) > Priorities.get(toppriority)){
						toppriority = c.IR_Priority__c;
					}
					if(c.Product_Family__c != null && !products.contains(c.Product_Family__c)){
						products += c.Product_Family__c + ';';
					}
					stubType = c.RecordType.Name;
				}
				if(products.contains(';')){
					products = products.substring(0, products.lastIndexOf(';')); //trim last comma;
				}
				Boolean isToUpdate = false;
				if(p.IR_Priority__c != toppriority){
					debug += ' IR Priority updated and current IR Priority is ' + p.IR_Priority__c;
					p.IR_Priority__c = toppriority;
					isToUpdate = true;
				}
				if(opened == 0 && total > 0 && total == contenttask && !p.isClosed && p.Region__c != null){
					debug += ' Status Closed and current status is ' + p.Status;
					p.Status = CLOSED;
					isToUpdate = true;
				}else if(total > 0 && opened == 0 && !p.isClosed){
					debug += ' Status Ready and current status is ' + p.Status;
					p.Status = READY;
					isToUpdate = true;
				}else if(opened > 0 && progress > 0 && (p.Status == OPEN || p.Status == READY || p.isClosed)){
					debug += ' Status In Progress and current status is ' + p.Status;
					p.Status = IN_PROGRESS;
					isToUpdate = true;
				}
				if((p.CSS_CRF_RequestType__c == null || p.CSS_CRF_RequestType__c == NOTDEFINED) && stubType != ''){
					debug += ' Request Type update ' + stubType;
					p.CSS_CRF_RequestType__c = stubType.replace('IOG ','');
					isToUpdate = true;
				}
				if(p.Total_Case_Count__c != total){
					debug += ' Total Case Count update to ' + total + ' from ' + p.Total_Case_Count__c;
					p.Total_Case_Count__c = total;
					isToUpdate = true;
				}
				if(p.CSS_ProductFamily__c != products){
					debug += ' Total Product family update to ' + products + ' from ' + p.CSS_ProductFamily__c;
					p.CSS_ProductFamily__c = products;
					isToUpdate = true;
				}
				if(isToUpdate){
					parenttoUpdate.add(p);
				}
			}
			if(!parenttoUpdate.isEmpty()){
				String ids = '';
				for(String s : incidentIds){
					ids += s + ';'; 
				}
				checkInvalidWebEmailExceptionAndTryAgain(parenttoUpdate, false, true, ' Fail Reason : Update the parent case fail! ids = ' + ids + debug);//update
			}
		}	
	}
	
	public static void updateStubsFromParentCases(Set<ID> ownerChanges, Set<ID> acctcontoriginamChanges, Set<ID> escalateChagnes){
		Set<ID> allIds = new Set<ID>();
		allIds.addAll(ownerChanges);
		allIds.addAll(acctcontoriginamChanges);
		allIds.addAll(escalateChagnes);		
		if(!allIds.isEmpty()){
			List<Case> allcases = [select Id, CreatedDate, Origin, OwnerId, AccountId, ContactId, Account_Manager__c, Status, Last_Queue_Owner__c
			                                , (select isClosed, Origin, Status, OwnerId, AccountId, ContactId, Account_Manager__c, IR_Priority__c, Last_Queue_Owner__c from cases )
			                       from Case where Id IN: allIds];
			List<Case> updateStubs = new List<Case>();
			for(Case p : allcases){
				List<Case> stubs = p.cases;
				Boolean isNewStandardCRFCase = p.Origin == CRF && p.Status != ESCALATED && p.CreatedDate.addSeconds(10) > DateTime.now();
				for(Case s : stubs){
					Boolean isChanged = false;
					if(ownerChanges.contains(p.Id) && s.Status == OPEN && (!isNewStandardCRFCase || (isNewStandardCRFCase && p.Last_Queue_Owner__c == s.Last_Queue_Owner__c))){
						s.OwnerId = p.OwnerId;
						isChanged = true;
					}
					if(acctcontoriginamChanges.contains(p.Id) && (s.AccountId != p.AccountId || s.ContactId != p.ContactId || s.Account_Manager__c != p.Account_Manager__c || s.Origin != p.Origin)){
						s.AccountId = p.AccountId;
						s.ContactId = p.ContactId;
						s.Account_Manager__c = p.Account_Manager__c;
						s.Origin = p.Origin;
						isChanged = true;
					}
					if(escalateChagnes.contains(p.Id) && Priorities.get(s.IR_Priority__c) < Priorities.get(ESCALATION) && !s.isClosed ){
						s.IR_Priority__c = ESCALATION;
						isChanged = true;
					}
					if(isChanged){
						updateStubs.add(s);
					}
				}
			}
			if(!updateStubs.isEmpty()){
				try{
					update updateStubs;
				}catch(Exception ex){
					throw new ConfigurationException(' Fail Reason : Automatically refresh stubs fail!');
					GeneralConstants.mailAnyExceptionToMe('CaseIOG Class', 'Automatically refresh stubs fail' , ex);
				}				
			}
		}
	}
	
	public static void sendEmailtoCaseOwnerforEvents(Set<ID> irCaseIds, Set<ID> statusCaseIds){
		Map<String, List<Case>> mapCases = new Map<String, List<Case>>();
		if(!irCaseIds.isEmpty()){
			EmailSentFromCaseHelperClass.sendEmailtoCaseOwnerIfDifferent(irCaseIds, 'IR Priority Change');
		}
		if(!statusCaseIds.isEmpty()){
			EmailSentFromCaseHelperClass.sendEmailtoCaseOwnerIfDifferent(statusCaseIds, 'Case Status Change');
		}	
	}
  
    // used in send email VF controller only
    public static String appendFooterToIOGEmails(Case c, ID uID, String body){
    	String footer = '';
    	String REG_CORE   = '\\[ATTi Case Number:\\s*[0-9]*\\s*/ Case Owner:\\s*[^/]*\\s*/ Sent By:\\s*[0-9a-zA-Z]*\\s*/ CSS Customer ID:.*\\]';
    	Pattern refPattern_core   = Pattern.compile(REG_CORE);
    	if(body != null && refPattern_core.matcher(body).find()){
    		body = body.replaceAll(REG_CORE, '');
    	}else{
    		footer = '\n\n';
    	}
    	if(c != null && AllIOGRecordTypeIDs.contains(c.RecordTypeId)){
    		Map<ID, User> users = new Map<ID, User>([select ATTUID__c from User where ID =: uID or ID =: c.OwnerId]);
    		String CaseOwnerATTUID;
    		if(users.get(c.OwnerId) != null){
    			CaseOwnerATTUID = users.get(c.OwnerId).ATTUID__c;
    		}else{
    			CaseOwnerATTUID = c.Owner.Name; // it's Queue name
    		}
    		String EmailSenderATTUID = users.get(uID).ATTUID__c;
    		String CSS_Customer_ID = (c.CSS_CustomerID__c == null)? '' : c.CSS_CustomerID__c;
    		footer += '[ATTi Case Number: ' + c.CaseNumber + ' / Case Owner: ' + CaseOwnerATTUID + ' / Sent By: ' + EmailSenderATTUID + ' / CSS Customer ID: ' + CSS_Customer_ID + ']';
    	}
    	return body + footer;
    } 
    
    public static void updateCaseFromFulfillmentItem(Map<ID, Fullfilment_Item__c> ifulfillsFromFItrigger, Map<ID, Case> casesFromCaseTrigger){
    	List<Case> cases;
    	Map<ID, Fullfilment_Item__c> fulfills;
    	if( ifulfillsFromFItrigger == null && casesFromCaseTrigger != null && !casesFromCaseTrigger.isEmpty()){ // from case trigger
    		isFITrigger = false;
    		cases = casesFromCaseTrigger.values();
    		Set<ID> fIds = new Set<ID>();
    		for(Case c : cases){
    			fIds.add(c.Fulfillment_Item__c);
    		} 
    		fulfills = new Map<ID, Fullfilment_Item__c>([ 
    		             select Status__c, Escalated__c, Owner.Name, Current_Step__c, RootProcessId__c, Fulfillment_Product_Group__c 
    		             from Fullfilment_Item__c where Id IN: fIds]);    		
    	}else if( ifulfillsFromFItrigger != null && !ifulfillsFromFItrigger.isEmpty() && casesFromCaseTrigger == null){ // from FI trigger
    		isFITrigger = true;
    		fulfills = ifulfillsFromFItrigger;
    		cases = [select Id, isClosed, Status, IR_Priority__c, Fulfillment_Item_Owner__c, Fulfillment_Item_Task__c, Fulfillment_Product_Group__c, RPI__c
    		                , Fulfillment_Item__c, Fulfillment_Item__r.Owner.Name 
    		         from Case where Fulfillment_Item__c IN: fulfills.keySet() and isClosed = false];
    	}
    	List<Case> updatedCases = new List<Case>();
    	if(cases != null && fulfills != null){
    		for(Case c : cases){
    			Fullfilment_Item__c fi = fulfills.get(c.Fulfillment_Item__c);
		    	if(fi != null && !c.IsClosed){
		    		Boolean isDifferent = false;
		    		if(c.Fulfillment_Item_Task__c != fi.Current_Step__c){
		    			c.Fulfillment_Item_Task__c = fi.Current_Step__c;
		    			isDifferent = true;
		    		}
		    		if(c.Fulfillment_Product_Group__c != fi.Fulfillment_Product_Group__c){
		    			c.Fulfillment_Product_Group__c = fi.Fulfillment_Product_Group__c;
		    			isDifferent = true;
		    		}
		    		String rpiToString = (fi.RootProcessId__c == null)? '' : '' + fi.RootProcessId__c ;
					if(rpiToString.contains('.')){
						rpiToString = rpiToString.substring(0, rpiToString.lastIndexOf('.')); //trim last .;
					}
		    		if(c.RPI__c != rpiToString){
		    			c.RPI__c = rpiToString;
		    			isDifferent = true;
		    		}
		    		if(!isFITrigger){ // from Case trigger
		    			if(c.Fulfillment_Item_Owner__c != fi.Owner.Name){
		    				c.Fulfillment_Item_Owner__c = fi.Owner.Name;
		    				isDifferent = true;
		    			}
		    		}else{ // from FI trigger
		    			if(c.Fulfillment_Item_Owner__c != c.Fulfillment_Item__r.Owner.Name){
		    				c.Fulfillment_Item_Owner__c = c.Fulfillment_Item__r.Owner.Name;
		    				isDifferent = true;
		    			}
		    			if(FI_END_STATUS.contains(fi.Status__c)){
		    				if(fi.Status__c == LIVE){
		    					c.Status = COMPLETE;
		    				}else if(fi.Status__c == OUTED){
		    					c.Status = CANCELLED;
		    				}else{
		    					c.Status = fi.Status__c;
		    				}
		    				isDifferent = true;
		    			}
		    			if(fi.Escalated__c && c.IR_Priority__c == STANDARD){
		    				c.IR_Priority__c = ESCALATION;
		    				isDifferent = true;
		    			}
		    			if(isDifferent){
		    				updatedCases.add(c);
		    			}
		    		}	
		    	}    			
    		}
    	}
    	if(!updatedCases.isEmpty()){
    		try{
    			update updatedCases;
    		}catch(Exception ex){
    			GeneralConstants.mailAnyExceptionToMe('FulfillmentItemUpdateIOGCases', 'RELATED CASE UPDATE' , ex);
    		}
    	}
    }
    
	private static ID getQueueId(String qName){
		if(queueIds == null ){
			queueIds = new Map<String, ID>();
			List<Group> qs  = [select Id, Name from Group where Name like 'IOG%' and Type = 'Queue'];
			for(Group q : qs){
				queueIds.put(q.Name, q.Id);
			}
		}
		ID rst = queueIds.get('IOG IR - Sales & Customer Support');	
		if(!queueIds.containsKey(qName)){
			List<Group> newQ  = [select Id, Name from Group where Name =: qName and Type = 'Queue'];
			if(newQ.isEmpty()){
				try{
					throw new ConfigurationException(' Fail Reason : "' + qName + '" Queues doesn\'t exist. ');
				}catch(Exception ex){
					GeneralConstants.mailAnyExceptionToMe('CaseIOGClass', ' Queue Name "' + qName + '" not found, assigned to IOG IR - Sales & Customer Support	Queue temporarily', ex);
				}
			}else{
				queueIds.put(newQ[0].Name, newQ[0].Id);
				rst = newQ[0].Id;
			}
		}else{
			rst = queueIds.get(qName);
		}
		return rst;
	}   

	private static User getLoginUser(){
		if(curLoginUser == null){
			curLoginUser = [select Id, Profile.Name, UserRole.Name, ATTUID__c from User where Id =: UserInfo.getUserId()];
		}
		return curLoginUser;
	}
	
	private static Boolean checkInvalidWebEmailExceptionAndTryAgain(List<Case> cases, Boolean isInsert, Boolean isUpdate, String errMsg){
		if((isInsert && isUpdate) || (!isInsert && !isUpdate)){
			throw new ConfigurationException(' Error : Need set either isInsert to true or isUpdate to true, Please contact your System Administrator! ');
		}
		try{
			if(isInsert) {insert cases;}
			else if(isUpdate) {update cases;}
		}catch(Exception e_outside){
			if(e_outside.getMessage() != null && e_outside.getMessage().contains('INVALID_EMAIL_ADDRESS')){
				for(Case c : cases){
					c.SuppliedEmail = 'null@null.null';
				}
				try{
					if(isInsert) {insert cases;}
					else if(isUpdate) {update cases;}
				}catch(Exception e_inside){	
					throw new ConfigurationException(errMsg + e_inside.getMessage());				
				}						
			}else{
				throw new ConfigurationException(errMsg + e_outside.getMessage());
			}
		}
		return true;
	} 
}